package models;

import global.ConfigData;

import java.io.IOException;
import java.io.Serializable;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;

import lib.Field;
import lib.persistence.NotFoundException;
import lib.persistence.commands.DeleteRow;
import lib.persistence.commands.GetAllModels;
import lib.persistence.commands.GetRow;
import lib.persistence.commands.GetModelsByField;
import lib.persistence.commands.InsertRow;


public class Project extends ModelAbstract
	implements Serializable
{
	/**
     * 
     */
    private static final long serialVersionUID = 1L;

	public enum Status {Not_Started, In_Progress, Complete, Canceled};

	// Private variables
	private int id;
	private String name;
	private Person creator;

	private int numMilestones;
	private int numDeliverables;
	private int currentMilestonesCompleted;
	private int currentDeliverablesCompleted;
	private float totalCompletionRate;
	private Date expectedCompletionDate;
	private Date finalCompletionDate;
	private Date createdAt;
	private List<Person> actors;
	private List<Milestone> milestones;
	private List<Publication> publications;
	private List<Deliverable> deliverables;
	private List<Event> events;
	private Status status;
	private String additonalNotes;


	//Default CTOR

	public Project()
	{
		
	}

	public Project(String name) 
	{
		super();
		this.name = name;
	}

	// CTOR is rather large. It requires a lot of data to be initialized. I'm afraid there's no other way around it. At some point if
	// we want to be able to import an object from the DB and put it into some kind of data structure for project creation or for modification
	// we will need an exhaustive CTOR. Otherwise you would have to call a number of accessor methods to create the object comletely.


	public Project(	int id, String name, String description, Person creator,
			int numMilestones, int numDeliverables, int currentMilestonesCompleted, 
			int currentDeliverablesCompleted, float totalCompletionRate, Date expectedCompletionDate, 
			Date finalCompletionDate, List<Person> actors, List<Milestone> milestones, 
			List<Publication> publications, List<Deliverable> deliverables, Status status,
			String additionalNotes)
	{
	    this(id, name, description, creator, numMilestones,
		    numDeliverables, currentMilestonesCompleted,
		    currentDeliverablesCompleted, totalCompletionRate,
		    expectedCompletionDate, finalCompletionDate, actors,
		    milestones, publications, deliverables, null, status,
		    additionalNotes);
	}

	public Project(	int id, String name, String description, Person creator,
			int numMilestones, int numDeliverables, int currentMilestonesCompleted, 
			int currentDeliverablesCompleted, float totalCompletionRate, Date expectedCompletionDate, 
			Date finalCompletionDate, List<Person> actors, List<Milestone> milestones, 
			List<Publication> publications, List<Deliverable> deliverables, List<Event> events,
			Status status, String additionalNotes)
	{
		this.id = id;
		this.name = name;
		this.numMilestones = numMilestones;
		this.creator = creator;
		this.numDeliverables = numDeliverables;
		this.currentMilestonesCompleted = currentMilestonesCompleted;
		this.currentDeliverablesCompleted = currentDeliverablesCompleted;
		this.totalCompletionRate = totalCompletionRate;
		this.expectedCompletionDate = new Date(expectedCompletionDate.getTime());
		this.finalCompletionDate = new Date (finalCompletionDate.getTime());
		setActors(actors);
		setMilestones(milestones);
		setPublications(publications);
		setDeliverables(deliverables);
		this.status = status;
		this.additonalNotes = additionalNotes;


	}

	public Project(Project o)
	{
		this.id = o.id;
		this.name = o.name;


		this.numMilestones = o.numMilestones;
		this.currentMilestonesCompleted = o.currentMilestonesCompleted;
		this.currentDeliverablesCompleted = o.currentDeliverablesCompleted;
		this.totalCompletionRate = o.totalCompletionRate;
		this.expectedCompletionDate = new Date(o.expectedCompletionDate.getTime());
		this.finalCompletionDate = new Date (o.finalCompletionDate.getTime());
		setActors(o.actors);
		setMilestones(o.milestones);
		setPublications(o.publications);
		setDeliverables(o.deliverables);
		this.status = o.status;
		this.additonalNotes = o.additonalNotes;

	}

	//Getters and Setters

	public Person getCreator() {
		return creator;
	}
	public void setAdditionalNotes(String notes)
	{
		this.additonalNotes = notes;
	}
	public void setCreator(Person creator) {
		this.creator = creator;
	}

	public int getCurrentMilestonesCompleted() {
		return currentMilestonesCompleted;
	}

	public void setCurrentMilestonesCompleted(int currentMilestonesCompleted) {
		this.currentMilestonesCompleted = currentMilestonesCompleted;
	}

	public int getCurrentDeliverablesCompleted() {
		return currentDeliverablesCompleted;
	}

	public void setCurrentDeliverablesCompleted(int currentDeliverablesCompleted) {
		this.currentDeliverablesCompleted = currentDeliverablesCompleted;
	}

	public double getTotalCompletionRate() {
		double completeCount = 0.0;
		double totalCount = 0.0;
		for(Deliverable d : getDeliverables())
		{
			if (d.getStatus().name().equalsIgnoreCase("complete"))
			{
				completeCount++;
			}
			totalCount++;
		}
		
		return (completeCount / totalCount);
	}

	public void setTotalCompletionRate(float totalCompletionRate) {
		this.totalCompletionRate = totalCompletionRate;
	}

	public Date getExpectedCompletionDate() {
		Date d = new Date();
		for(Deliverable dlv : getDeliverables())
		{
			if (dlv.getCreationDate().after(d))
				d = dlv.getCreationDate();
		}
		return new Date(d.getTime() + 100 * 60 * 60 * 24 * 30);
	}

	public void setExpectedCompletionDate(Date expectedCompletionDate) {
		this.expectedCompletionDate = expectedCompletionDate;
	}

	public Date getFinalCompletionDate() {
		return new Date(getExpectedCompletionDate().getTime() + 100 * 60 * 60 * 24 * 30); 
	}

	public void setFinalCompletionDate(Date finalCompletionDate) {
		this.finalCompletionDate = finalCompletionDate;
	}

	public List<Person> getActors() {
		actors  = new ArrayList<Person>();
		GetRow gr = new GetRow();
		
		try {
			for (Integer pid : gr.findIntByColumnWhereIsInt("project_id", this.getId(), ConfigData.PROJECT_PERSON_TABLE, "person_id"))
				actors.add(Person.find(pid));
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return actors;
	}

	public void setActors(List<Person> actors) {
		this.actors = new ArrayList<Person>(actors.size());

		Iterator<Person> iterator = actors.iterator();
		while (iterator.hasNext())
			this.actors.add(iterator.next());
		
	}

	public List<Milestone> getMilestones() {
		milestones  = new ArrayList<Milestone>();
		GetRow gr = new GetRow();
		
		try {
			for (Object obj : gr.findObjectByWhere(ConfigData.MILESTONE_TABLE, "obj", "deliverable_ids", ""+getId()))
				milestones.add((Milestone) obj);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return milestones;

	}

	public void setMilestones(List<Milestone> milestones) {
		this.milestones = new ArrayList<Milestone>(milestones.size());

		Iterator<Milestone> iterator = milestones.iterator();
		while (iterator.hasNext())
			this.milestones.add(iterator.next());
	}

	public List<Publication> getPublications() {
		return publications;
	}

	public void setPublications(List<Publication> publications) {

		this.publications = new ArrayList<Publication>(publications.size());

		Iterator<Publication> iterator = publications.iterator();
		while (iterator.hasNext())
			this.publications.add(iterator.next());
	}

	public List<Deliverable> getDeliverables() {
		deliverables  = new ArrayList<Deliverable>();
		GetRow gr = new GetRow();
		
		try {
			for (Object obj : gr.findObjectByWhere(ConfigData.DELIVERABLE_TABLE, "obj", "project_id", ""+getId()))
				deliverables.add((Deliverable) obj);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return deliverables;
	}

	public void setDeliverables(List<Deliverable> deliverables) {
		this.deliverables = new ArrayList<Deliverable>(deliverables.size());

		Iterator<Deliverable> iterator = deliverables.iterator();
		while (iterator.hasNext())
			this.deliverables.add(iterator.next());
	}
	
	/**
	 * @param events the events to set
	 */
	public void setEvents(List<Event> events) {
	    this.events = events;
	}

	/**
	 * @return the events
	 */
	public List<Event> getEvents() {
	    return events;
	}

	public Status getStatus() {
		return status;
	}

	public void setStatus(Status status) {
		this.status = status;
	}

	public String getAdditonalNotes() {
		return additonalNotes;
	}

	public void setAdditonalNotes(String additonalNotes) {
		this.additonalNotes = additonalNotes;
	}

	public int getNumber_Milestones() 
	{
		return getMilestones().size();
	}

	public void setNumber_Milestones(int Number_Milestones) 
	{
		this.numMilestones = Number_Milestones;
	}

	public int getNumber_deliverables() 
	{
		return getDeliverables().size();
	}

	public void setNumber_deliverables(int Number_deliverables) 
	{
		this.numDeliverables = Number_deliverables;
	}

	public String getName() 
	{
		return name;
	}

	public void setName(String Name) 
	{
		this.name = Name;
	}

	// End getters and setters

	// Equals method. Will be expanded upon when given a chance to.
	// Right now it's there to make it so the Unit tests work

	public boolean equals (Project o)
	{
		if (o == null)
			return false;
		if (this.id != o.id)
			return false;
		if (!this.creator.equals(o.creator))
			return false;
		if (!this.name.equals(o.name))
			return false;
		if (this.numMilestones != o.numMilestones)
			return false;
		if (this.numDeliverables != o.numDeliverables)
			return false;
		if (this.currentMilestonesCompleted != o.currentMilestonesCompleted)
			return false;
		if (this.currentDeliverablesCompleted != o.currentDeliverablesCompleted)
			return false;
		if (this.totalCompletionRate != o.totalCompletionRate)
			return false;
		if (this.expectedCompletionDate.getTime() != o.expectedCompletionDate.getTime())
			return false;
		if (this.finalCompletionDate.getTime() != o.finalCompletionDate.getTime())
			return false;		
		if (this.status != o.status)
			return false;
		if (!this.additonalNotes.equals(o.additonalNotes))
			return false;


		if (this.actors != null && o.actors != null)
		{

			Iterator<Person> iterator1 = this.actors.iterator();
			Iterator<Person> iterator2 = o.actors.iterator();

			while (iterator1.hasNext() && iterator2.hasNext())
			{
				if (!iterator1.next().equals(iterator2.next()))
					return false;
			}

			if (iterator1.hasNext() || iterator2.hasNext())
				return false;
		}
		else
			if (this.actors!= null || o.actors!=null)
				return false;


		if (this.milestones != null && o.milestones != null)
		{
			Iterator<Milestone>iterator3= this.milestones.iterator();
			Iterator<Milestone>iterator4= o.milestones.iterator();

			while (iterator3.hasNext() && iterator4.hasNext())
			{
				if (!iterator3.next().equals(iterator4.next()))
					return false;
			}

			if (iterator3.hasNext() || iterator4.hasNext())
				return false;
		}
		else 
			if (this.milestones != null || o.milestones != null)
				return false;


		if (this.publications != null && o.publications != null)
		{
			Iterator<Publication>iterator5 = this.publications.iterator();
			Iterator<Publication> iterator6 = o.publications.iterator();

			while (iterator5.hasNext() && iterator6.hasNext())
			{
				if (!iterator5.next().equals(iterator6.next()))
					return false;
			}

			if (iterator5.hasNext() || iterator6.hasNext())
				return false;
		}
		else 
			if (this.publications != null || o.publications != null)
				return false;

		if (this.deliverables != null && o.deliverables != null)
		{
			Iterator<Deliverable>iterator7 = this.deliverables.iterator();
			Iterator<Deliverable>iterator8 = o.deliverables.iterator();

			while (iterator7.hasNext() && iterator8.hasNext())
			{
				if (!iterator7.next().equals(iterator8.next()))
					return false;
			}

			if (iterator7.hasNext() || iterator8.hasNext())
				return false;
		}
		else
			if (this.deliverables != null || o.deliverables != null)
				return false;
		

		return true;
	}

	// Allows the system to add individual items to the lists without having to recreate a new list.

	public void addEvent(Event e)
	{
	    events.add(e);
	}
	
	public void addPerson(Person p)
	{
		actors.add(p);
	}

	public void addMilestone(Milestone m)
	{
		milestones.add(m);
	}
	
	public void addDeliverable(Deliverable d)
	{
		deliverables.add(d);
	}

	public void addPublication(Publication p)
	{
		publications.add(p);
	}
	public void doSomethingImportantThatCanBeTested()
	{
		// stuff happens
		notifyObservers();
	}
	@Override
	protected ArrayList<Field> getSqlReadyFields() {
		ArrayList<Field> al = new ArrayList<Field>();
		Field f;
		f = new Field();
		al.add(f.setName("obj").setType("blob").setObjValue(this));

		// title
		f = new Field();
		al.add(f.setName("name").setType("string").setValue(this.getName()));
		
		// creator
		f = new Field();
		al.add(f.setName("createdBy").setType("integer").setValue(""+this.getCreator().getId()));
		
		// project id
		f = new Field();
		al.add(f.setName("id").setType("integer").setValue(""+ this.id));
				
		// status
		
		f = new Field();
		al.add(f.setName("status").setType("string").setValue(this.status.toString()));
		
		return al;
	}
	public int create() throws SQLException
	{
		return super.create();
	}
	public int update() throws SQLException
	{
		refreshProjectPersonMapping();
		return super.update();
	}

	private void refreshProjectPersonMapping() throws SQLException {
		// clear all many-to-many maps between project and people
		DeleteRow dr = new DeleteRow();
		dr.executeWhereInt("project_id",ConfigData.PROJECT_PERSON_TABLE, this.getId());
		// recreate any that are relevant
		Field f = new Field();
		ArrayList<Field> al;
		InsertRow ir = new InsertRow();
		for(Person p : actors)
		{
			al = new ArrayList<Field>();
			f = new Field();
			al.add(f.setName("project_id").setType("integer").setValue(""+getId()));
			f = new Field();
			al.add(f.setName("person_id").setType("integer").setValue(""+p.getId()));

			ir.execute(al, ConfigData.PROJECT_PERSON_TABLE);
		}
	}
	@Override
	protected String getTableName()
	{
		return ConfigData.PROJECT_TABLE;
	}

	@Override
	public int getId() {
		return id;
	}

	@Override
	public void setId(int id) {
		this.id = id;		
	}
	public void setStatus(String status)
	{
		this.status = Status.valueOf(status);
	}
	public void setId(String id)
	{
		this.setId(Integer.parseInt(id));
	}
	public void setCreatedAt(String createdAt) 
	{
		SimpleDateFormat d = new SimpleDateFormat("MM/dd/yy");
		try {
			this.createdAt = d.parse(createdAt);
		} catch (ParseException e) {
		}
	
	}
	public void setCreatedAt(Date createdAt) {
		this.createdAt = createdAt;
	}

	public Date getCreatedAt() {
		return createdAt;
	}
	public void setCreatedBy(String personId)
	{
		try {
			this.setCreatedBy(Person.find(Integer.parseInt(personId)));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
    public void setCreatedBy(Person creator) {
    	this.creator = creator;
     }


	public static Project find(int id) throws NotFoundException, SQLException, IOException, ClassNotFoundException 
	{
		GetRow gr = new GetRow();
		Project p = (Project) gr.findObjectById(id, ConfigData.PROJECT_TABLE, "obj");
		
		return p;
	}

	public static List<Project> findAll() throws SQLException, IOException, ClassNotFoundException {
		GetAllModels gam = new GetAllModels();
		
		List<Project> projects = new ArrayList<Project>();
		for (Object object : gam.execute(ConfigData.PROJECT_TABLE,"obj"))
			projects.add((Project) object);
		
		return projects;
	}

	public void fireActor(int id) throws SQLException {
		DeleteRow dr = new DeleteRow();
		dr.executeCustomWhere(ConfigData.PROJECT_PERSON_TABLE , String.format("(project_id = %d AND person_id = %d)",getId(),id));
	}
}
